#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>

unsigned count;

enum DigitRepeat {repeat_no, repeat_yes};

enum AnsTip {tip_no, tip_yes};

enum AnsTipType {start_char=1, start_hit, end_blow};


//二进制的最小位和最高位.这里的最小位对应que/ans的左边,方向正好是相反的
enum Direct {lowest, highest};

//question
char que[10];

//answer
char ans[10];

//可回答次数(<0无限次)
int tryno;

//游戏数字位数
int digit;

//游戏数字是否可重复
enum DigitRepeat digitRepeat;

//是否提示
enum AnsTip tip;

//初始化函数
void initialize(){
    count=0;
    srand(time(NULL));
}

//h
void debug_print_bin(long unsigned, unsigned);

void debug(long unsigned, long unsigned, int);

int sleep(unsigned ms){
    clock_t s = clock();

    clock_t e;

    do {
        if ((e=clock()) == (clock_t)-1) {
            return 0;
        }

    } while (1000.0 * (e-s)/CLOCKS_PER_SEC < ms);

    return 1;
}

/**
 * 生成题目
 *
 * @param const* que 题目空间指针
 * @return void
 */
void gen_que(char *que){
    int i,j;
    int val;

    i=0;
    for (; i < digit; ++i) {
        do {
            val= '0' + rand()%10;

            if (digitRepeat == repeat_yes) break;

             j=0;
             for (; j<i; ++j) {
                 if (que[j] == val)
                     break;
             }

        } while (j < i);

        que[i] = val;
    }
}

void print_que(){
    char *tmp=que;
    while (*tmp)
        printf("%c ",*tmp++);

    putchar('\n');
}

int verify_ans(int bit, enum DigitRepeat rep);

/**
 * 输入答案数字
 *
 * @param char* ans 答案空间指针
 * @param enum repeat 游戏数字是否允许重复
 *
 * @return void
 */
void gen_ans(char *ans){
    int bit = digit;
    enum DigitRepeat repeat = digitRepeat;


    do {
        sleep(1000);

        printf("请输入您的答案:");
        scanf("%s", ans);

        switch (verify_ans(bit, repeat)) {
            case 0:
                return;

            case 1: printf("数字长度必须是:%d\n", bit); break;
            case 2: printf("只能输入数字哟!\n"); break;
            case 3: printf("不能含有重复的数字!\n"); break;
        }

    } while (1);

}

/**
 * @param int   bit 数字位数
 * @param enum  rep 是否允许重复数字o
 *
 * @return int
 */
int verify_ans(int bit, enum DigitRepeat rep)
{
    if (strlen(ans) != bit) {
        return 1;
    }

    int i=0;
    for (;i<bit; ++i) {
        if (ans[i] < '0' || ans[i] > '9')
            return 2;

        if (rep == repeat_yes) continue;

        int j=0;
        for (;j<i; j++) {
            if (*(ans+j) == *(ans+i))
                return 3;
        }

    }

    return 0;
}

/**
 * 判断回答数字
 * 如果long的设置位=que长度,则胜出
 *
 * @param char* que 问题数字
 * @param char* ans 回答数字
 * @param long* blow
 * @param long* hit
 *
 * @return int
 */
int judge(const char* que, const char* ans, long *blow, long *hit){
    int len=digit;

    //清空历史记录
    *blow=0;
    *hit=0;

    //hit优先计算
    for (int i=0; i<len; ++i) {
        if (que[i] == ans[i])
            *hit |= (1 << i);
    }

    //计算blow
    //要记住blow位指的是ans
    for (int j=0; j<len; ++j) {
        if (*hit & (1 << j)) continue;

        for (int k=0;k<len; ++k) {
            long cbin = 1 << k;

            //必须保证当前k位没有hit或blow
            if (que[j] == ans[k] && !(*hit & cbin) && !(*blow & cbin)) {
                *blow |= cbin;

                //repeat_no 后面不需要再判断了
                //repeat_yes 一个blow位只使用一次
                break;
            }
        }
    }

    return *hit == len ? 1 : 0;
}

/**
 * 统计设置位
 * @param long dest
 *
 * @return int
 */
int bit_count(long dest){
    int c = 0;
    while (dest) {
        if (dest & 1) ++c;
        dest >>= 1;
    }

    return c;
}

/**
 * 计算边界设置位的位置
 * 为了方便使用,这里的最小位是0
 *
 * @param long dest
 * @param int  direct lowest最小位 highest最高位
 *
 * @return int -1没有设置位
 */
int bit_border(long dest, enum Direct direct){
    int pos = -1;
    while (dest) {
        ++pos;
        if (dest & 1 && direct == lowest) break;

        dest >>= 1;
    }

    return pos;
}

void debug(long unsigned q, long unsigned a, int len){
    char *qq = que, *aa = ans;

    printf("\n------------ debug ------------\n");
    printf(" que: \t\t");
    while (*qq)
        printf("%c ", *qq++);


    printf("\n ans: \t\t");
    while (*aa)
        printf("%c ", *aa++);

    putchar('\n');

    printf(" hitbit: \t");
    debug_print_bin(q, len);

    printf(" blowbit: \t");
    debug_print_bin(a,len);

    printf("------------- /debug ----------\n\n");

}

void debug_print_bin(long unsigned v, unsigned bit){
    unsigned i=0;
    while (v) {
        ++i;
        printf("%c ", !(v & 1) ? '0' : '1');
        v >>= 1;
    }

    while (i++ < bit) {
        printf("%c ", '0');
    }

    putchar('\n');
}


void ready() {
    int step = 0;

    while (step < 4) {
        int val = 0;

        switch (step) {
            case 0:
                printf("允许回答次数(默认-1,无限次数):");
                scanf("%d", &val);

                tryno = val == 0 ? -1 : val;

                break;
            case 1:
                printf("游戏数字的位数(4~8位,默认4位):");
                scanf("%d", &val);
                digit = val<4 || val>8 ? 4 : val;

                break;
            case 2:
                printf("游戏数字是否允许重复?(0不允许；1允许)");
                scanf("%d", &val);
                if (val > 1 || val <0) continue;
                digitRepeat = val;

                break;
            case 3:
                printf("是否需要提示(0不需要；1需要)");
                scanf("%d", &val);
                if (val > 1 || val < 0) continue;
                tip = val;

                break;
        }

        ++step;
        getchar();  //防止输入错误死循环
    }

}

void summary()
{

    printf("\n%30s\n", "~珠玑妙算~");
    printf("*********************************************\n");

    if (tryno <=0) {
        printf(" 不限制回答次数\n");

    } else {
        printf(" 允许回答%d次\n", tryno);
    }

    printf(" 游戏数字为%d位数\n", digit);

    printf(" 游戏数字%s允许重复\n", digitRepeat? "": "不");

    printf(" 游戏%s提示\n", tip ? "每回答3次会": "不会");

    printf("*********************************************\n\n");
}


void print_tip(enum AnsTipType type, const char* que, const char* ans, long blow, long hit){
    switch (type) {
        case start_char:
            printf("最左边的数字是%c", que[0]);

            break;
        case start_hit:
            if (!hit) {
                printf("hit目前为空,您可以选择其他提醒!");
            } else {
                printf("hit第一位数字是%c", ans[ bit_border(hit, lowest) ]);
            }

            break;
        case end_blow:
            if (!blow) {
                printf("blow目前为空,您可以选择其他提醒方式!");
            } else {
                printf("blow最后一位数字是%c ", ans[ bit_border(blow, highest) ]);
            }
            break;

        default:
            printf("您放弃了使用提示.");
    }
}


/**
 * 游戏过程函数
 * 相当于过程总调度器
 */
int game(){

    //使用设置位记录命中
    //注意: 这里的位是从左向右
    //example:
    //  hit(0101): 表示hit第一位和第三位命中
    //  blow(1000): 表示blow第四位数字命中
    long hit, blow;

    gen_que(que);

    do{
        //给出提示
        if (tip == tip_yes && count && !(count % 3)) {
            int type;
            puts("您已经连续回答三次了,可以使用提示");
            printf("1-查看第一个数字;\n2-hit的第一个数字;\n3-blow的最后一个数字;\n其他-不提示;\n");
            printf("请选择:");

            scanf("%d", &type);
            print_tip(type, que, ans, blow, hit);

            printf("\n");
        }

        //选择认输
        if (count && !(count % 25)) {
            printf("您已经猜了%d次了,您可以(1-认输 其他-继续):\n", count);
            int choice;
            scanf("%d", &choice);
            if (choice == 1) {
                puts("很遗憾,您选择了认输!");
                break;
            }
        }

        ++count;

        gen_ans(ans);
        printf("你的回答: %s\n", ans);

        judge(que, ans, &blow, &hit);

        if (bit_count(hit) == digit) {
            return 1;
        }

        //debug(hit, blow, digit);

        printf("有效数字有%d个, 其中%d个位置正确.\n", bit_count(hit+blow), bit_count(hit));

        sleep(600);

        if (--tryno > 0) {
            printf("还有%d次机会!\n", tryno);
        }

        putchar('\n');

    } while (tryno);

    return 0;
}


void result(int result){
    if (result) {
        printf("^_^恭喜,你赢了!\n");
        printf("-----------------------------\n");
        printf("正确答案是%s,你一共用了%d次\n", que, count);

    } else {
        printf("-_-很遗憾,你输了!\n");
        printf("-----------------------------\n");
        printf("正确答案是%s!\n", que);
    }

    printf("\n-------------------------------\n\n");

}

int main(void)
{
    initialize();

    ready();

    summary();

    result(game());

    return 0;
}
